﻿/**
 * Mouse mapping GUI and input polling.
 * 
 * Author: Caleb Leak
 * Date:   12.30.2010
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace ControllerToMouse
{
    /**
     * Mouse mapping GUI.
     * 
     */

    public partial class MouseMapper : Form
    {
        public Dictionary<string, float> vars;
        InputController input;

        Vector2 delta;

        DateTime lastUpdate;

        string varFile;

        /**
         * Constructs a mouse mapping GUI with all variables loaded from a file.
         * 
         */

        public MouseMapper()
        {
            InitializeComponent();
            vars = new Dictionary<string, float>();

            delta = Vector2.Zero;

            varFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            varFile += @"\360ToMouse";

            if (!Directory.Exists(varFile))
                Directory.CreateDirectory(varFile);

            varFile += @"\vars.txt";

            loadVas();

            // Default values
            setDefault("horizontal", 1);
            setDefault("vertical", 1);
            setDefault("pollRate", 1000);
            setDefault("invertHorizontal", 0);
            setDefault("invertVertical", 0);
            setDefault("swapXY", 0);
            setDefault("stick", 0);

            // Load variables and format input
            horizontalSensNumeric.DecimalPlaces = 2;
            horizontalSensNumeric.Increment = (decimal)0.01;
            horizontalSensNumeric.Value = (decimal)getVar("horizontal");

            verticalSensNumeric.DecimalPlaces = 2;
            verticalSensNumeric.Increment = (decimal)0.01;
            verticalSensNumeric.Value = (decimal)getVar("vertical");

            swapXY.Checked = (getVar("swapXY") == 1);
            invertHorizontal.Checked = (getVar("invertHorizontal") == 1);
            invertVertical.Checked = (getVar("invertVertical") == 1);

            radioStickLeft.Checked = (getVar("stick") == 0);
            radioStickRight.Checked = (getVar("stick") == 1);

            // Setup our polling timer
            pollTimer.Interval = (int)Math.Max(1000 / getVar("pollRate"), 1);
            pollRateNumeric.Value = (decimal)getVar("pollRate");
            pollTimer.Start();
            lastUpdate = DateTime.Now;

            input = new InputController();
        }

        /**
         * Sets the default value for a given variable.  If the variable 
         * exists already then it's value will not be overwritten.
         * 
         * @param varName the name of the variable to set the default value of.
         * @param value the default value for the variable.
         */

        private void setDefault(string varName, float value)
        {
            if(!vars.ContainsKey(varName))
                setVar(varName, value);
        }

        /**
         * Saves all variables out to a file so they can later be loaded.
         * 
         */

        public void saveVars()
        {
            FileStream stream = new FileStream(varFile, FileMode.Create);

            StreamWriter writer = new StreamWriter(stream);
            foreach (string varName in vars.Keys)
            {
                writer.WriteLine(varName + "=" + vars[varName]);
            }

            writer.Flush();
            stream.Flush();
            stream.Close();
        }

        /**
         * Loads all variables from a save file.
         * 
         */

        public void loadVas()
        {
            vars.Clear();
            if (File.Exists(varFile))
            {
                FileStream stream = new FileStream(varFile, FileMode.Open);

                StreamReader reader = new StreamReader(stream);

                while (reader.Peek() > 0)
                {
                    string line = reader.ReadLine();
                    string[] splitLine = line.Split("=".ToCharArray());
                    vars.Add(splitLine[0], float.Parse(splitLine[1]));
                }

                stream.Close();
            }
        }

        /**
         * Sets the value of a variable or creates it if it doesen't exist.
         * 
         * @param var the name of the variable to be set.
         * @param value the value to store in the variable.
         */

        public void setVar(string var, float value)
        {
            if (!vars.ContainsKey(var))
                vars.Add(var, value);
            else
                vars[var] = value;

            saveVars();
        }

        /**
         * Gets the value of an existing variable (throws an exception if it
         * doesn't exist).
         * 
         * @param varName the variable to get the value of.
         * @return the requested variable value.
         */

        public float getVar(string varName)
        {
            return vars[varName];
        }

        /**
         * Handles timer ticks by polling the controller and updating the mouse
         * location.
         * 
         */

        private void pollTimer_Tick(object sender, EventArgs e)
        {
            updateMouseDelta();

            // Get mouse movement in pixels
            int moveX = (int)Math.Round(delta.X);
            int moveY = (int)Math.Round(delta.Y);

            // Update the mouse only if movement is at least one pixel,
            // otherwise just keep accumulating it.
            if (moveX != 0 || moveY != 0)
            {
                // Mouse movement is always relative to the current mouse
                // location.
                int mouseX = input.getMouseX() + moveX;
                int mouseY = input.getMouseY() + moveY;

                input.mouseMove(mouseX, mouseY);

                // Remove only what we moved so partial pixels of movement can
                // be kept.
                delta -= new Vector2(moveX, moveY);
            }
        }

        /**
         * Updates the delta from the mouse location.
         * 
         */

        private void updateMouseDelta()
        {
            DateTime now = DateTime.Now;
            float timePassed = (float)(now - lastUpdate).TotalMilliseconds / 1000;
            lastUpdate = now;

            // Grab the gamepad and make sure it's connected
            GamePadState gamePad = GamePad.GetState(Microsoft.Xna.Framework.PlayerIndex.One, 
                GamePadDeadZone.IndependentAxes);
           
            if (gamePad.IsConnected)
            {
                // Grab the correct stick
                Vector2 stick;
                if (radioStickLeft.Checked)
                    stick = gamePad.ThumbSticks.Left;
                else
                    stick = gamePad.ThumbSticks.Right;

                // Adjust the sensitivity
                stick *= new Vector2((float)horizontalSensNumeric.Value, (float)verticalSensNumeric.Value) * 100;

                // Swap X and Y if requested
                if (swapXY.Checked)
                {
                    float swap = stick.X;
                    stick.X = stick.Y;
                    stick.Y = swap;
                }

                // Invert axis
                if (invertHorizontal.Checked)
                    stick.X *= -1;

                if (invertVertical.Checked)
                    stick.Y *= -1;

                stick *= timePassed;
                
                // Accumulate movement over multiple calls
                delta += stick;
            }
        }

        private void horizontalSensNumeric_ValueChanged(object sender, EventArgs e)
        {
            setVar("horizontal", (float)horizontalSensNumeric.Value);
        }

        private void verticalSensNumeric_ValueChanged(object sender, EventArgs e)
        {
            setVar("vertical", (float)verticalSensNumeric.Value);
        }

        private void pollRateNumeric_ValueChanged(object sender, EventArgs e)
        {
            pollTimer.Interval = (int)Math.Max(1000 / pollRateNumeric.Value, 1);
            setVar("pollRate", (float)pollRateNumeric.Value);
        }

        private void invertHorizontal_CheckedChanged(object sender, EventArgs e)
        {
            if (invertHorizontal.Checked)
                setVar("invertHorizontal", 1);
            else
                setVar("invertHorizontal", 0);
        }

        private void invertVertical_CheckedChanged(object sender, EventArgs e)
        {
            if (invertVertical.Checked)
                setVar("invertVertical", 1);
            else
                setVar("invertVertical", 0);
        }

        private void swapXY_CheckedChanged(object sender, EventArgs e)
        {
            if (swapXY.Checked)
                setVar("swapXY", 1);
            else
                setVar("swapXY", 0);
        }

        private void radioStickLeft_CheckedChanged(object sender, EventArgs e)
        {
            if (radioStickLeft.Checked)
                setVar("stick", 0);
            else
                setVar("stick", 1);
        }
    }
}
